import {
  defineComponent,
  ComponentPropsOptions,
  ExtractPropTypes,
  SetupContext,
  getCurrentInstance,
  provide,
  inject,
} from "vue";

/**
 * 
 * @param options 
 * @description designComponent这个函数，可以使得我们在获取组件的引用（ref）以及
 * 注入（inject）组件提供（provide）的数据时，
 * 自动获取正确的类型提示以及约束，无需额外编写组件类型声明
 */
export function designComponent<
  PropsOptions extends Readonly<ComponentPropsOptions>,
  Props extends Readonly<ExtractPropTypes<PropsOptions>>,
  Refer
>(options: {
  name?: string;
  props?: PropsOptions;
  providerRefer?: boolean;
  setup: (
    props: Props,
    setupContext: SetupContext
  ) => {
    refer?: Refer;
    render: () => any;
  };
}) {
  const { setup, providerRefer, ...leftOptions } = options;
  return {
    ...defineComponent({
      ...leftOptions,
      setup(props: Props, setupContext: SetupContext) {
        const ctx = getCurrentInstance() as any;
        if (!setup) {
          console.error("designComponent: setup is required!");
          return false;
        }
        const { refer, render } = setup(props, setupContext);
        ctx._refer = refer;

        if (providerRefer) {
          if (!leftOptions.name) {
            console.error("designComponent: name is required when providerRefer is true!");
            return false;
          }
          provide(`@@${leftOptions.name}`, refer)
        }
        return render
      },
    } as any),
    use: {
      ref: (refName: string) => {
        const {ctx} = (getCurrentInstance() as any)
        return {
          get value() {
            return ((ctx as any).$refs[refName].$._refer) as Refer | null
          }
        }
      },
      inject: (defaultValue?: any) => {
        return inject(`@@${leftOptions.name}`, defaultValue) as Refer
      }
    }
  };
}
